package joe;

import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.SymbolLookup;
import java.lang.invoke.MethodHandle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.stb.STBImage;
import org.lwjgl.system.MemoryUtil;
import org.tinylog.Logger;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static java.lang.foreign.ValueLayout.JAVA_LONG;

public class ResourceTexture implements Resource {
	private static final @Nullable MethodHandle mhAvifDecode;
	private static final @Nullable MethodHandle mhAvifFree;

	static {
		SymbolLookup libAvif;
		try {
			libAvif = SymbolLookup.libraryLookup(System.mapLibraryName("avif"), Arena.global());
		} catch (Exception e) { // ignore
			libAvif = null;
		}
		if (libAvif != null) {
			var linker = Linker.nativeLinker();
			// int avifDecode(const void* pSrcData, int srcSize, void** ppDstData, int* pDstWidth, int* pDstHeight, int* pDstDepth)
			mhAvifDecode = linker.downcallHandle(libAvif.find("avifDecode").orElseThrow(),
					FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_INT, JAVA_LONG, JAVA_LONG, JAVA_LONG, JAVA_LONG));
			// void avifFree(void* p)
			mhAvifFree = linker.downcallHandle(libAvif.find("avifFree").orElseThrow(),
					FunctionDescriptor.ofVoid(JAVA_LONG));
		} else {
			mhAvifDecode = null;
			mhAvifFree = null;
		}
	}

	private final long ptr;
	private final int width;
	private final int height;
	private final int depth; // [1,4]
	private final @NotNull Runnable cleaner;

	public ResourceTexture(long ptr, int width, int height, int depth, @NotNull Runnable cleaner) {
		this.ptr = ptr;
		this.width = width;
		this.height = height;
		this.depth = depth;
		this.cleaner = cleaner;
	}

	@Override
	public long getDataPtr() {
		return ptr;
	}

	@Override
	public long getDataSize() {
		return (long)width * height * depth;
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	public int getDepth() {
		return depth;
	}

	@Override
	public @NotNull Runnable getCleaner() {
		return cleaner;
	}

	public static @Nullable ResourceTexture load(@NotNull String path) {
		var mbb = ResourceMapFile.loadMapFile(path);
		if (mbb == null)
			return null;
		try {
			var srcDataPtr = MemoryUtil.memAddress(mbb);
			var srcSize = mbb.remaining();
			if (srcSize >= 12 && UnsafeUtil.getLong(srcDataPtr + 4) == 0x66_69_76_61_70_79_74_66L) { // "ftypavif"
				if (mhAvifDecode == null) {
					Logger.error("ResourceTexture: library 'avif' is not loading for decoding: {}", path);
					return null;
				}
				var tmpBuf = Engine.current().getTmpBuf().ptr();
				int r = (int)mhAvifDecode.invokeExact(srcDataPtr, srcSize, tmpBuf, tmpBuf + 8, tmpBuf + 12, tmpBuf + 16);
				if (r != 0) {
					Logger.error("ResourceTexture: avifDecode failed({}) for: {}", r, path);
					return null;
				}
				var ptr = UnsafeUtil.getLong(tmpBuf);
				return new ResourceTexture(ptr, UnsafeUtil.getInt(tmpBuf + 8), UnsafeUtil.getInt(tmpBuf + 12),
						UnsafeUtil.getInt(tmpBuf + 16), () -> {
					try {
						//noinspection DataFlowIssue
						mhAvifFree.invokeExact(ptr);
					} catch (Throwable e) { // rethrow
						UnsafeUtil.forceThrow(e);
					}
				});
			} else {
				var pw = new int[1];
				var ph = new int[1];
				var pd = new int[1];
				var ptr = STBImage.nstbi_load_from_memory(srcDataPtr, srcSize, pw, ph, pd, 0);
				if (ptr == 0) {
					Logger.error("ResourceTexture: nstbi_load_from_memory failed: {}", STBImage.stbi_failure_reason());
					return null;
				}
				return new ResourceTexture(ptr, pw[0], ph[0], pd[0], () -> STBImage.nstbi_image_free(ptr));
			}
		} catch (Throwable e) { // logger.error
			Logger.error(e, "ResourceTexture: load exception");
			return null;
		} finally {
			UnsafeUtil.freeMappedByteBuffer(mbb);
		}
	}
}
